package com.atguigu.tingshu.search.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.pinyin.PinyinUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.Buckets;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsBucket;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.NestedQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.*;
import co.elastic.clients.json.JsonData;
import com.atguigu.tingshu.album.AlbumFeignClient;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.model.album.*;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.repository.AlbumInfoIndexRepository;
import com.atguigu.tingshu.search.repository.SuggestIndexRepository;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserFeignClient;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RedissonClient;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;


@Slf4j
@Service
public class SearchServiceImpl implements SearchService {

    private final AlbumInfoIndexRepository albumInfoIndexRepository;
    private final SuggestIndexRepository suggestIndexRepository;
    private final AlbumFeignClient albumFeignClient;
    private final UserFeignClient userFeignClient;
    private final ThreadPoolExecutor threadPoolExecutor;
    private final ElasticsearchClient elasticsearchClient;
    private final RedisTemplate<Object, Object> redisTemplate;
    private final RedissonClient redissonClient;

    public SearchServiceImpl(AlbumInfoIndexRepository albumInfoIndexRepository, SuggestIndexRepository suggestIndexRepository, AlbumFeignClient albumFeignClient, UserFeignClient userFeignClient, ThreadPoolExecutor threadPoolExecutor, ElasticsearchClient elasticsearchClient, RedisTemplate<Object, Object> redisTemplate, RedissonClient redissonClient) {
        this.albumInfoIndexRepository = albumInfoIndexRepository;
        this.suggestIndexRepository = suggestIndexRepository;
        this.albumFeignClient = albumFeignClient;
        this.userFeignClient = userFeignClient;
        this.threadPoolExecutor = threadPoolExecutor;
        this.elasticsearchClient = elasticsearchClient;
        this.redisTemplate = redisTemplate;
        this.redissonClient = redissonClient;
    }


    @Override
    public void upperAlbum(Long albumId) {
        // 创建索引库文档对象
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();
        // 封装专辑信息
        CompletableFuture<AlbumInfo> albumInfoFuture = CompletableFuture.supplyAsync(() -> {
            Result<AlbumInfo> albumInfoResult = this.albumFeignClient.getAlbumInfoById(albumId);
            if (ResultCodeEnum.FAIL.getCode().equals(albumInfoResult.getCode())) {
                throw new GuiguException(ResultCodeEnum.REMOTE_ERROR);
            }
            AlbumInfo albumInfo = albumInfoResult.getData();
            Assert.notNull(albumInfo, "专辑不存在，专辑ID：" + albumId);
            BeanUtil.copyProperties(albumInfo, albumInfoIndex);
            // 专辑上架时间
            albumInfoIndex.setCreateTime(new Date());

            // 封装专辑标签信息
            List<AlbumAttributeValue> albumAttributeValueList = albumInfo.getAlbumAttributeValueList();
            if (CollectionUtil.isNotEmpty(albumAttributeValueList)) {
                List<AttributeValueIndex> attributeValueIndexList = albumAttributeValueList.stream()
                        .map(albumAttributeValue -> new AttributeValueIndex(
                                albumAttributeValue.getAttributeId(), albumAttributeValue.getValueId()
                        ))
                        .toList();
                albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);
            }
            return albumInfo;
        }, this.threadPoolExecutor);


        // 封装分类信息
        CompletableFuture<Void> categoryViewFuture = albumInfoFuture.thenAcceptAsync(albumInfo -> {
            Result<BaseCategoryView> categoryViewResult = this.albumFeignClient.getCategoryView(albumInfo.getCategory3Id());
            if (ResultCodeEnum.FAIL.getCode().equals(categoryViewResult.getCode())) {
                throw new GuiguException(ResultCodeEnum.SERVICE_ERROR);
            }
            BaseCategoryView baseCategoryView = categoryViewResult.getData();
            Assert.notNull(baseCategoryView, "分类不存在，分类ID：" + albumInfo.getCategory3Id());
            albumInfoIndex.setCategory1Id(baseCategoryView.getCategory1Id());
            albumInfoIndex.setCategory2Id(baseCategoryView.getCategory2Id());
        }, this.threadPoolExecutor);


        // 封装announcerName,主播名称
        CompletableFuture<Void> UserInfoFuture = albumInfoFuture.thenAcceptAsync(albumInfo -> {
            Long userId = albumInfo.getUserId();
            Result<UserInfoVo> userInfoVoResult = this.userFeignClient.getUserInfoVoByUserId(userId);
            if (ResultCodeEnum.FAIL.getCode().equals(userInfoVoResult.getCode())) {
                throw new GuiguException(ResultCodeEnum.SERVICE_ERROR);
            }
            UserInfoVo userInfoVo = userInfoVoResult.getData();
            Assert.notNull(userInfoVo, "主播用户不存在，用户ID：" + userId);
            albumInfoIndex.setAnnouncerName(userInfoVo.getNickname());
        }, this.threadPoolExecutor);


        // 封装专辑统计信息以及计算专辑热度 HotScore
//        Result<AlbumStatVo> albumStatVoResult = this.albumFeignClient.getAlbumStatVoByAlbumId(albumId);
//        if (ResultCodeEnum.FAIL.getCode().equals(albumStatVoResult.getCode())) {
//            throw new GuiguException(ResultCodeEnum.SERVICE_ERROR);
//        }
//        AlbumStatVo albumStatVo = albumStatVoResult.getData();
        // 因为数据库专辑统计数据几乎全为0，所以采用产生随机值 以及专辑热度
        CompletableFuture<Void> statFuture = CompletableFuture.runAsync(() -> {
            // 随机为专辑产生播放量，订阅量，购买量，评论量
            int playStatNum = RandomUtil.randomInt(1000, 2000);
            int subscribeStatNum = RandomUtil.randomInt(500, 1000);
            int buyStatNum = RandomUtil.randomInt(200, 400);
            int commentStatNum = RandomUtil.randomInt(100, 200);
            albumInfoIndex.setPlayStatNum(playStatNum);
            albumInfoIndex.setSubscribeStatNum(subscribeStatNum);
            albumInfoIndex.setBuyStatNum(buyStatNum);
            albumInfoIndex.setCommentStatNum(commentStatNum);

            //  基于统计值计算出专辑得分 为不同统计类型设置不同权重
            BigDecimal commentStatScore = new BigDecimal(commentStatNum).multiply(new BigDecimal("0.4"));
            BigDecimal buyStatScore = new BigDecimal(buyStatNum).multiply(new BigDecimal("0.3"));
            BigDecimal subscribeStatScore = new BigDecimal(subscribeStatNum).multiply(new BigDecimal("0.2"));
            BigDecimal playStatScore = new BigDecimal(playStatNum).multiply(new BigDecimal("0.1"));
            BigDecimal hotScore = commentStatScore.add(buyStatScore).add(subscribeStatScore).add(playStatScore);
            albumInfoIndex.setHotScore(hotScore.doubleValue());
        }, this.threadPoolExecutor);

        // 异步任务编排，等待所有任务完成
        CompletableFuture.allOf(
                albumInfoFuture,
                categoryViewFuture,
                UserInfoFuture,
                statFuture
        ).join();

        // 将索引库文档对象存入索引库
        this.albumInfoIndexRepository.save(albumInfoIndex);
        // 专辑上架时新增提词记录到提词索引库
        this.saveSuggestIndex(albumInfoIndex);
        // 将上架的专辑 ID 存入布隆过滤器
        this.redissonClient.getBloomFilter(RedisConstant.ALBUM_BLOOM_FILTER).add(albumId);

    }

    @Override
    public void lowerAlbum(Long albumId) {
        this.albumInfoIndexRepository.deleteById(albumId);
    }

    /**
     * 新增提词记录到提词索引库
     *
     * @param albumInfoIndex 提词文档索引库对象
     */
    @Override
    public void saveSuggestIndex(AlbumInfoIndex albumInfoIndex) {
        SuggestIndex suggestIndex = new SuggestIndex();
        // 提示词id和专辑id一致
        suggestIndex.setId(albumInfoIndex.getId().toString());
        suggestIndex.setTitle(albumInfoIndex.getAlbumTitle());
        // 汉字提词
        String[] keyword = new String[]{suggestIndex.getTitle()};
        suggestIndex.setKeyword(new Completion(keyword));
        // 拼音提词
        String[] keywordPinyin = new String[]{PinyinUtil.getPinyin(suggestIndex.getTitle(), StrUtil.EMPTY)};
        suggestIndex.setKeywordPinyin(new Completion(keywordPinyin));
        // 首字母提词
        String[] keywordSequence = new String[]{PinyinUtil.getFirstLetter(suggestIndex.getTitle(), StrUtil.EMPTY)};
        suggestIndex.setKeywordSequence(new Completion(keywordSequence));
        // 保存提示词到提词索引库
        this.suggestIndexRepository.save(suggestIndex);
    }

    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        try {
            // 构建 SearchRequest 检索请求对象，封装DSL查询条件
            SearchRequest searchRequest = this.buildSearchRequest(albumIndexQuery);

            // 调用原生ES客户端对象发送请求，进行检索后获取响应结果
            SearchResponse<AlbumInfoIndex> searchResponse = this.elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);

            // 解析响应结果，封装成 AlbumSearchResponseVo对象并返回
            return this.parseSearchResponse(searchResponse, albumIndexQuery);
        } catch (IOException e) {
            log.error("[搜索服务]查询条件：{}，站内检索异常：{}", albumIndexQuery, e.getMessage(), e);
            throw new GuiguException(ResultCodeEnum.SERVICE_ERROR);
        }
    }

    @Override
    public SearchRequest buildSearchRequest(AlbumIndexQuery albumIndexQuery) {
        // 1.创建检索请求构造器对象，封装检索索引库及有检索DSL语句
        SearchRequest.Builder searchRequestBuilder = new SearchRequest.Builder();
        // 设置索引库
        searchRequestBuilder.index(SystemConstant.ALBUM_INDEX_NAME);

        // 2.设置请求体参数"query",处理查询条件（关键字、分类、标签）
        // 2.1 创建最外层bool组合条件对象
        BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();

        // 2.2 处理关键字查询条件 采用must必须满足，包含bool组合三个子条件，三个子条件should或者关系
        String keyword = albumIndexQuery.getKeyword();
        if (StrUtil.isNotBlank(keyword)) {
            // 构建关键字查询条件对象
            Query keywordQuery = this.buildKeywordQuery(keyword);
            boolQueryBuilder.must(keywordQuery);
        }

        // 2.3 处理分类ID查询条件
        this.addTermFilter(boolQueryBuilder, "category1Id", albumIndexQuery.getCategory1Id());
        this.addTermFilter(boolQueryBuilder, "category2Id", albumIndexQuery.getCategory2Id());
        this.addTermFilter(boolQueryBuilder, "category3Id", albumIndexQuery.getCategory3Id());

        // 2.4 处理标签查询条件(可能有多个)
        List<String> attributeList = albumIndexQuery.getAttributeList();
        // 判断是否提交标签过滤条件
        if (CollectionUtil.isNotEmpty(attributeList)) {
            // 每遍历一个标签，设置filter中的一个Nested条件
            attributeList.forEach(attribute -> {
                String[] split = attribute.split(":");
                if (split.length == 2) {
                    // 构建Nested查询条件
                    String attributeId = split[0];
                    String valueId = split[1];
                    NestedQuery.Builder nestedQueryBuilder = new NestedQuery.Builder();
                    nestedQueryBuilder.path("attributeValueIndexList")
                            .query(query -> query.bool(
                                    bool -> bool
                                            .filter(filter -> filter.term(
                                                    term -> term.field("attributeValueIndexList.attributeId").value(attributeId)
                                            ))
                                            .filter(must -> must.term(
                                                    term -> term.field("attributeValueIndexList.valueId").value(valueId)
                                            ))
                            ));
                    Query attributeNestedQuery = nestedQueryBuilder.build()._toQuery();
                    // 将Nested查询条件封装到最外层bool查询的filter字段中
                    boolQueryBuilder.filter(attributeNestedQuery);
                }
            });
        }

        //2.5 将最外层bool组合条件对象设置到请求体参数"query"中
        searchRequestBuilder.query(boolQueryBuilder.build()._toQuery());

        // 3.设置请求体参数 "from","size" 处理分页
        Integer from = (albumIndexQuery.getPageNo() - 1) * albumIndexQuery.getPageSize();
        Integer size = albumIndexQuery.getPageSize();
        searchRequestBuilder.from(from).size(size);


        // 4.设置请求体参数"_source" 控制返回的字段
        searchRequestBuilder.source(source -> source.filter(
                filter -> filter.excludes(SystemConstant.ALBUM_SEARCH_EXCLUDE_FIELD)
        ));

        // 5.设置请求体参数"highlight" 高亮显示匹配的字段(用户输入的关键字)
        if (StrUtil.isNotBlank(keyword)) {
            searchRequestBuilder.highlight(highlight -> highlight
                    .fields(SystemConstant.ALBUM_HIGHLIGHT_FIELDS.stream()
                            .collect(Collectors.toMap(
                                    field -> field,
                                    field -> HighlightField.of(f -> f.fragmentSize(20))
                            )))
                    .preTags("<span style='color:red'>")
                    .postTags("</span>")
            );
        }

        // 6.设置请求体参数 "sort" 处理排序（综合、播放量、发布时间）
        String order = albumIndexQuery.getOrder();
        //  6.1 判断参数排序是否提交。提交形式： 排序字段（1：综合 2：播放量 3：发布时间）:排序方式
        if (StrUtil.isBlank(order)) {
            // 如果没有排序参数，直接返回
            return searchRequestBuilder.build();
        }
        String[] split = order.split(":");
        if (split.length != 2) {
            // 参数格式错误，直接返回
            return searchRequestBuilder.build();
        }

        // 6.2 获取排序字段
        String sortField = switch (split[0]) {
            case "1" -> "hotScore";
            case "2" -> "playStatNum";
            case "3" -> "createTime";
            default -> null;
        };
        if (sortField == null) {
            // 排序字段非法，直接返回
            return searchRequestBuilder.build();
        }
        String asc = SortOrder.Asc.jsonValue();
        // 6.3 获取排序方式，默认降序
        SortOrder orderType = asc.equals(split[1]) ? SortOrder.Asc : SortOrder.Desc;

        // 6.4 设置排序
        searchRequestBuilder.sort(sort ->
                sort.field(field -> field.field(sortField).order(orderType)));

        // 7.调用检索请求构造器 searchRequestBuilder 返回检索请求对象
        return searchRequestBuilder.build();
    }

    /**
     * 构建关键字搜索的布尔查询条件
     *
     * @param keyword 关键字
     * @return 返回构建好的 Query 对象
     */
    private Query buildKeywordQuery(String keyword) {
        if (StrUtil.isBlank(keyword)) {
            // 如果关键字为空，则不添加查询条件
            return null;
        }
        // 构建多字段 should 查询，match匹配专辑标题、简介，term精确匹配主播名称
        BoolQuery.Builder keywordQueryBuilder = new BoolQuery.Builder();
        keywordQueryBuilder.should(query -> query.match(match -> match.field("albumTitle").query(keyword)));
        keywordQueryBuilder.should(query -> query.match(match -> match.field("albumIntro").query(keyword)));
        keywordQueryBuilder.should(query -> query.term(term -> term.field("announcerName").value(keyword)));

        return keywordQueryBuilder.build()._toQuery();
    }


    /**
     * 为指定字段添加 term 过滤查询到 boolQueryBuilder 中
     *
     * @param boolQueryBuilder 构建中的布尔查询构造器
     * @param field            字段名
     * @param value            字段值
     */
    private void addTermFilter(BoolQuery.Builder boolQueryBuilder, String field, Long value) {
        if (value != null) {
            boolQueryBuilder.filter(filter -> filter.term(term -> term.field(field).value(value)));
        }
    }


    @Override
    public AlbumSearchResponseVo parseSearchResponse(SearchResponse<AlbumInfoIndex> searchResponse, AlbumIndexQuery albumIndexQuery) {
        // 1. 创建专辑信息搜索结果Vo对象
        AlbumSearchResponseVo searchResponseVo = new AlbumSearchResponseVo();
        // 2. 封装Vo对象
        // 设置每页记录数
        Integer pageSize = albumIndexQuery.getPageSize();
        searchResponseVo.setPageSize(pageSize);
        // 设置当前页码
        Integer pageNo = albumIndexQuery.getPageNo();
        searchResponseVo.setPageNo(pageNo);
        // 设置总记录数
        Optional.ofNullable(searchResponse.hits().total())
                .map(TotalHits::value)
                .ifPresent(searchResponseVo::setTotal);
        // 计算并设置总页数
        long totalPages = (searchResponseVo.getTotal() + pageSize - 1) / pageSize;
        searchResponseVo.setTotalPages(totalPages);

        // 设置检索信息列表
        List<Hit<AlbumInfoIndex>> hits = searchResponse.hits().hits();
        List<AlbumInfoIndexVo> albumInfoIndexVoList = hits.stream()
                .map(hit -> {
                    // 将 AlbumInfoIndex 转换为 AlbumInfoIndexVo
                    AlbumInfoIndex albumInfoIndex = hit.source();
                    AlbumInfoIndexVo albumInfoIndexVo = BeanUtil.copyProperties(albumInfoIndex, AlbumInfoIndexVo.class);
                    // 高亮片段替换
                    Map<String, List<String>> highlightMap = hit.highlight();
                    if (MapUtil.isEmpty(highlightMap)) {
                        return albumInfoIndexVo;
                    }
                    highlightMap.forEach((field, text) -> {
                        if ("albumTitle".equals(field)) {
                            albumInfoIndexVo.setAlbumTitle(text.get(0));
                        } else if ("albumIntro".equals(field)) {
                            albumInfoIndexVo.setAlbumIntro(text.get(0));
                        }
                    });
                    return albumInfoIndexVo;
                })
                .toList();
        searchResponseVo.setList(albumInfoIndexVoList);

        // 3. 返回封装后的搜索结果
        return searchResponseVo;
    }

    @Override
    public List<Map<String, Object>> getTopCategory3HotAlbums(Long category1Id) {
        try {
            // 创建期望返回的集合
            List<Map<String, Object>> topCategory3HotAlbums = new ArrayList<>();

            // 1.根据 category1Id 远程调用专辑服务获取置顶前7个三级分类集合
            Result<List<BaseCategory3>> topBaseCategory3Result = this.albumFeignClient.findTopBaseCategory3ByCategory1Id(category1Id);
            if (ResultCodeEnum.FAIL.getCode().equals(topBaseCategory3Result.getCode())) {
                throw new GuiguException(ResultCodeEnum.SERVICE_ERROR);
            }
            List<BaseCategory3> topBaseCategory3List = topBaseCategory3Result.getData();
            if (CollectionUtil.isEmpty(topBaseCategory3List)) {
                // 一级分类未包含置顶三级分类
                throw new GuiguException(ResultCodeEnum.CATEGORY1_NOT_CONTAIN_TOP_CATEGORY3);
            }


            // 2.查询索引库获取置顶三级分类（7个）不同置顶三级分类下热度前6个的专辑列表
            // 2.1.创建检索请求构造器对象，封装检索索引库及有检索DSL语句
            SearchRequest.Builder searchRequestBuilder = new SearchRequest.Builder();
            // 设置索引库
            searchRequestBuilder.index(SystemConstant.ALBUM_INDEX_NAME);
            // 结果大小（Size）：控制返回内容
            searchRequestBuilder.size(0);
            // 设置检索条件query
            // 三级分类di列表类型转化为 List<FieldValue>
            List<FieldValue> catrgory3IdList = topBaseCategory3List.stream()
                    .map(baseCategory3 -> FieldValue.of(baseCategory3.getId())).toList();
            searchRequestBuilder.query(query -> query.terms(
                    terms -> terms.field("category3Id")
                            .terms(termField -> termField.value(catrgory3IdList))
            ));

            // 设置聚合条件 Aggregations， 最外层聚合分组（category3IdAgg）
            searchRequestBuilder.aggregations("category3IdAgg", agg1 -> agg1.terms(
                            terms -> terms.field("category3Id").size(10)
                    )
                    // 第二层聚合条件每个分组取Top 6热门专辑（top6Agg）
                    .aggregations("top6Agg", agg2 -> agg2.topHits(
                            topHits -> topHits.size(6).sort(
                                    sort -> sort.field(
                                            field -> field.field("hotScore").order(SortOrder.Desc)
                                    )
                            )
                    )));
            SearchRequest searchRequest = searchRequestBuilder.build();
            // 发起检索请求，获取检索响应结果
            SearchResponse<AlbumInfoIndex> searchResponse = this.elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);

            // 3.解析搜索结果，封装 topCategory3HotAlbums
            // 获取三级分类聚合结果对象
            Aggregate category3Agg = searchResponse.aggregations().get("category3IdAgg");
            // 获取三级分类聚合“桶”集合 buckets字段数据
            Buckets<LongTermsBucket> buckets = category3Agg.lterms().buckets();
            List<LongTermsBucket> bucketList = buckets.array();
            if (CollectionUtil.isNotEmpty(bucketList)) {
                // 将三级分类集合转为Map<baseCategory3Id，baseCategory3> 方便解析结果封装三级分类对象
                Map<Long, BaseCategory3> category3Map = topBaseCategory3List.stream()
                        .collect(Collectors.toMap(BaseCategory3::getId, baseCategory3 -> baseCategory3));
                // 遍历“桶”集合，每遍历一个元素处理某个置顶三级分类热门专辑
                topCategory3HotAlbums = bucketList.stream().map(bucket -> {
                    // 获取三级分类id
                    long category3Id = bucket.key();
                    // 根据 category3Id 获取Map中的三级分类对象 baseCategory3
                    BaseCategory3 baseCategory3 = category3Map.get(category3Id);

                    // 获取Map中热门专辑列表 List
                    Aggregate top6Agg = bucket.aggregations().get("top6Agg");
                    List<Hit<JsonData>> hits = top6Agg.topHits().hits().hits();
                    List<AlbumInfoIndex> hotAlbumList = hits.stream()
                            .map(hit -> Optional.ofNullable(hit.source())
                                    .map(source -> source.to(AlbumInfoIndex.class))
                                    .orElse(new AlbumInfoIndex()))
                            .toList();
                    // 封装数据
                    return Map.of(
                            "baseCategory3", baseCategory3,
                            "list", hotAlbumList
                    );
                }).toList();
            }

            return topCategory3HotAlbums;
        } catch (IOException e) {
            log.error("[搜索服务]首页热门专辑服务异常：{}", e.getMessage(), e);
            throw new GuiguException(ResultCodeEnum.SERVICE_ERROR);
        }
    }

    @Override
    public List<String> completeSuggest(String keyword) {
        try {
            // 1.根据用户输入关键字构建suggest查询条件发起请求获取检索结果
            SearchResponse<SuggestIndex> suggestResponse = this.elasticsearchClient.search(
                    search -> search.index(SystemConstant.SUGGEST_INDEX_NAME)
                            .suggest(suggests -> suggests
                                    .suggesters("suggestKeyword", sug -> sug.prefix(keyword).completion(
                                            c -> c.field("keyword").size(10).skipDuplicates(true)
                                                    .fuzzy(fuzzy -> fuzzy.fuzziness("auto"))
                                    ))
                                    .suggesters("suggestPinyin", sug -> sug.prefix(keyword).completion(
                                            c -> c.field("keywordPinyin").size(10).skipDuplicates(true)
                                                    .fuzzy(fuzzy -> fuzzy.fuzziness("auto"))
                                    ))
                                    .suggesters("suggestSequence", sug -> sug.prefix(keyword).completion(
                                            c -> c.field("keywordSequence").size(10).skipDuplicates(true)
                                                    .fuzzy(fuzzy -> fuzzy.fuzziness("auto"))
                                    )))
                    , SuggestIndex.class);

            // 2.解析提示词响应结果,如果提示词结果记录数大于10，只取前十个
            // 三个自定义建议参数检索的结果可能有重复的，需要进行去重，这里使用有序的 LinkedHashSet 进行去重
            Set<String> result = new LinkedHashSet<>();
            // 提取各建议字段并合并到 result 中
            result.addAll(extractSuggestions(suggestResponse.suggest().get("suggestKeyword")));
            result.addAll(extractSuggestions(suggestResponse.suggest().get("suggestPinyin")));
            result.addAll(extractSuggestions(suggestResponse.suggest().get("suggestSequence")));
            log.info("suggest 提示词结果：{}", result);
            if (result.size() >= 10) {
                result = result.stream().limit(10).collect(Collectors.toSet());
                return new ArrayList<>(result);
            }

            // 3.如果提示词结果记录数小于10，采用match分词查询专辑索引库（album_info）
            SearchResponse<AlbumInfoIndex> matchResponse = this.elasticsearchClient.search(
                    search -> search.index(SystemConstant.ALBUM_INDEX_NAME)
                            .query(query -> query.match(
                                    match -> match.field("albumTitle").query(keyword)
                            ))
                    , AlbumInfoIndex.class);
            // 解析match检索响应结果，并合并到 result 中
            List<Hit<AlbumInfoIndex>> hits = matchResponse.hits().hits();
            if (CollectionUtil.isEmpty(hits)) {
                log.info("match匹配为空， 提示词结果：{}", result);
                return new ArrayList<>(result);
            }
            for (Hit<AlbumInfoIndex> hit : hits) {
                AlbumInfoIndex albumInfoIndex = hit.source();
                if (albumInfoIndex != null) {
                    // 补充关键字补全词列表
                    result.add(albumInfoIndex.getAlbumTitle());
                }
                // 最多补充到10个
                if (result.size() >= 10) {
                    break;
                }
            }

            log.info("补充后 提示词结果：{}", result);
            return new ArrayList<>(result);
        } catch (IOException e) {
            log.error("[搜索服务]用户关键字补全服务异常：{}", e.getMessage(), e);
            throw new GuiguException(ResultCodeEnum.SERVICE_ERROR);
        }
    }


    /**
     * 提取 Suggestion 中的 title 字段并返回一个 Set 集合（使用 Stream + Optional 实现）
     *
     * @param suggestions Elasticsearch 的 Suggestion 列表
     * @return 包含 title 的 Set 集合
     */
    private Set<String> extractSuggestions(List<Suggestion<SuggestIndex>> suggestions) {
        // 如果 suggestions == null，会返回一个空流
        return Optional.ofNullable(suggestions).stream()
                // 将 List<Suggestion> 展开为多个 Suggestion 对象（即扁平化一级）
                .flatMap(Collection::stream)
                .map(Suggestion::completion)
                // 提取 options 字段
                .map(CompletionSuggest::options)
                // 过滤掉空列表
                .filter(CollectionUtil::isNotEmpty)
                // List<CompletionSuggestOption> 展开为多个 CompletionSuggestOption 对象
                .flatMap(Collection::stream)
                // 获取 SuggestIndex 对象
                .map(CompletionSuggestOption::source)
                // 过滤掉 SuggestIndex 为null的流
                .filter(Objects::nonNull)
                // 提取 title 字段
                .map(SuggestIndex::getTitle)
                // 收集结果到 Set<String>
                .collect(Collectors.toSet());
    }

    @Override
    @Scheduled
    public void updateLatelyAlbumRanking() {
        try {
            // 远程调用专辑服务获取所有1级分类列表
            Result<List<BaseCategory1>> allCategory1Result = albumFeignClient.getAllCategory1();
            if (ResultCodeEnum.FAIL.getCode().equals(allCategory1Result.getCode())) {
                throw new GuiguException(ResultCodeEnum.REMOTE_ERROR);
            }
            List<BaseCategory1> category1List = allCategory1Result.getData();
            Assert.notNull(category1List, "一级分类为空");

            // 创建排序维度集合
            List<String> sortFieldList = List.of("hotScore", "playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum");
            // 循环遍历1级分类列表，获取该一级分类下5种不同排序方式榜单专辑
            for (BaseCategory1 category1 : category1List) {
                Long category1Id = category1.getId();
                String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;
                BoundHashOperations<Object, Object, List<AlbumInfoIndex>> hashOps = this.redisTemplate.boundHashOps(key);
                for (String sortField : sortFieldList) {
                    // 检索索引库获取排行榜数据
                    SearchResponse<AlbumInfoIndex> searchResponse = this.elasticsearchClient.search(
                            search -> search.index(SystemConstant.ALBUM_INDEX_NAME)
                                    .query(query -> query.term(
                                            term -> term.field("category1Id").value(category1Id)
                                    ))
                                    .sort(sort -> sort.field(
                                            field -> field.field(sortField).order(SortOrder.Desc)
                                    ))
                                    .size(10)
                            , AlbumInfoIndex.class);

                    // 获取当前分类下排行榜专辑列表
                    List<Hit<AlbumInfoIndex>> hits = searchResponse.hits().hits();
                    if (CollectionUtil.isEmpty(hits)) {
                        log.info("当前分类'{}'下排行榜专辑列表为空", category1.getName());
                        continue;
                    }
                    List<AlbumInfoIndex> albumInfoIndexList = hits.stream().map(Hit::source).toList();
                    // 将榜单专辑列表存入 缓存 中
                    hashOps.put(sortField, albumInfoIndexList);
                }
            }
        } catch (IOException e) {
            log.error("[搜索服务]更新所有分类下排行榜服务异常：{}", e.getMessage(), e);
            throw new GuiguException(ResultCodeEnum.SERVICE_ERROR);
        }

    }

    @Override
    public List<AlbumInfoIndex> getRankingList(Long category1Id, String dimension) {
        String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;
        BoundHashOperations<Object, Object, List<AlbumInfoIndex>> hashOps = this.redisTemplate.boundHashOps(key);
        Boolean flag = hashOps.hasKey(dimension);
        if (Boolean.TRUE.equals(flag)) {
            List<AlbumInfoIndex> albumInfoIndexList = hashOps.get(dimension);
            log.info("Redis value type: {}", albumInfoIndexList.getClass().getName());
            log.info("Redis value content: {}", albumInfoIndexList);
            return albumInfoIndexList;
        }
        return List.of();
    }
}
